#ifndef Magnum_Trade_LightData_h
#define Magnum_Trade_LightData_h
/*
    This file is part of Magnum.

    Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
                2020, 2021, 2022, 2023, 2024, 2025
              Vladimír Vondruš <mosra@centrum.cz>

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
*/

/** @file
 * @brief Class @ref Magnum::Trade::LightData
 */

#include "Magnum/Magnum.h"
#include "Magnum/Math/Color.h"
#include "Magnum/Trade/visibility.h"

namespace Magnum { namespace Trade {

/**
@brief Light type
@m_since_latest

@see @ref LightData::type()
*/
enum class LightType: UnsignedByte {
    /* Zero reserved for an invalid value */

    /**
     * Ambient light, without any position, direction or attenuation. Meant to
     * be added to ambient color in Phong workflows, has no use in physically
     * based workflows.
     * @m_since_latest
     */
    Ambient = 1,

    /**
     * Light at a position that is infinitely far away, emitted in a direction
     * of negative Z axis. The rotation is inherited from absolute object
     * transformation; scale and position don't affect the light in any way.
     * Because the light is at infinite distance, it's not attenuated in any
     * way.
     * @m_since_latest
     */
    Directional,

    #ifdef MAGNUM_BUILD_DEPRECATED
    /**
     * Directional light.
     * @m_deprecated_since_latest Use @ref LightType::Directional instead.
     */
    Infinite CORRADE_DEPRECATED_ENUM("use LightType::Directional instead") = Directional,
    #endif

    /**
     * Point light, emitting light in all directions. The position is inherited
     * from absolute object transformation; scale and rotation don't affect the
     * light in any way. Brightness attenuates depending on the
     * @ref LightData::range() value.
     */
    Point,

    /**
     * Spot light, emitting light in a cone in direction of local negative Z
     * axis. The position and rotation is inherited from absolute object
     * transformation; scale doesn't affect the light in any way. The angle and
     * falloff of the cone is defined using @ref LightData::innerConeAngle()
     * and @ref LightData::outerConeAngle() and brightness attenuates depending
     * on the @ref LightData::range() value.
     */
    Spot
};

/** @debugoperatorenum{LightType} */
MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, LightType value);

/**
@brief Light data

Provides access to light properties. Populated instances of this class are
returned from @ref AbstractImporter::light() and
can be passed to @ref AbstractSceneConverter::add(const LightData&, Containers::StringView).
Similarly to other @ref Trade types, the internal representation is fixed upon
construction and doesn't allow any modification afterwards.

@section Trade-LightData-usage Usage

The class exposes light parameters in a way that makes sense as a whole,
allowing to reduce branching in application code --- e.g., a light defined by
just its range has the quadratic attenuation factor set to one, with constant
and linear attenuation being zero, or spot cone angles are the full circle
everything except spotlights.

@section Trade-LightData-populating Populating an instance

You can choose a constructor overload that matches the subset of input
parameters and let the class set the rest implicitly. For example, a
@ref LightType::Point light constructed using a range will have
@ref attenuation() implicitly set to @cpp {1.0f, 0.0f, 1.0f} @ce and cone
angles to @cpp 360.0_degf @ce:

@snippet Trade.cpp LightData-populating-range

Or, a @ref LightType::Spot light constructed from a constant / linear /
quadratic attenuation will have @ref range() implicitly set to
@ref Constants::inf():

@snippet Trade.cpp LightData-populating-attenuation

And a @ref LightType::Directional light that doesn't attenuate can be
constructed without either, causing @ref attenuation() to be
@cpp {1.0f, 0.0f, 0.0f} @ce and @ref range() @ref Constants::inf(), cancelling
out the attenuation equation:

@snippet Trade.cpp LightData-populating-none

@section Trade-LightData-attenuation Attenuation calculation

To support all common lighting calculations, the class exposes parameters in a
combined equation containing both constant / linear / quadratic attenuation
@f$ \color{m-success} K_c @f$ / @f$ \color{m-success} K_l @f$ /
@f$ \color{m-success} K_q @f$ and a range parameter @f$ \color{m-info} R @f$
over a distance @f$ d @f$: @f[
    F_{att} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{{\color{m-success} K_c} + {\color{m-success} K_l} d + {\color{m-success} K_q} d^2}
@f]

In most cases you'll have the light data using either one or the other
approach. The classic constant/linear/quadratic equation allows for most
control, but because the attenuated intensity never really reaches zero, it
makes light culling optimizations hard to perform. In this case the
@ref range() is set to @ref Constants::inf(): @f[
    F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(}1 \mathbin{\color{m-dim}-} {\color{m-dim} (\frac{d}{R})^4, 0, 1)^2}}{{\color{m-success} K_c} + {\color{m-success} K_l} d + {\color{m-success} K_q} d^2} = \frac{1}{{\color{m-success} K_c} + {\color{m-success} K_l} d + {\color{m-success} K_q} d^2}
@f]

The range-based equation approaches zero when @f$ {\color{m-info} R} = d @f$
and provides a good tradeoff for performance while staying mostly
physically-based. This is modelled after the glTF [KHR_lights_punctual](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual#range-property)
extension, which in turn is based on the [UE4 implementation](https://github.com/KhronosGroup/glTF/pull/1223#issuecomment-387956919).
In this case, @ref attenuation() is set to
@cpp {1.0f, 0.0f, 1.0f} @ce, the constant factor is present in order to prevent
the function from exploding to infinity when @f$ d \to \infty @f$. @f[
    F_{att} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{{\color{m-success} K_c} + {\color{m-dim} K_l d} \mathbin{\color{m-dim} +} {\color{m-success} K_q} d^2} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{1 + d^2}
@f]

If @f$ {\color{m-info} R} \to \infty @f$ as well, the equation reduces down to
a simple inverse square: @f[
    F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(} 1 \mathbin{\color{m-dim} -} {\color{m-dim} (\frac{d}{R})^4, 0, 1)^2}}{{\color{m-success} K_c} + {\color{m-dim} K_l d} \mathbin{\color{m-dim} +} {\color{m-success} K_q} d^2} = \frac{1}{1 + d^2}
@f]

As a special case, a @ref LightType::Directional light is defined by
@ref attenuation() set to @cpp {1.0f, 0.0f, 0.0f} @ce and @ref range() to
@ref Constants::inf() --- thus without any attenuation: @f[
    F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(} 1 \mathbin{\color{m-dim} -} {\color{m-dim} (\frac{d}{R})^4, 0, 1)^2}}{{\color{m-success} K_c} \mathbin{\color{m-dim} +} {\color{m-dim} K_l d + K_q d^2}} = 1
@f]

@section Trade-LightData-units Units

To follow physically-based principles in lighting calculation, intensity is
assumed to be in in *candela* (lm/sr) for @ref LightType::Point and
@ref LightType::Spot, and in *lux* (lm/m<sup>2</sup>) for
@ref LightType::Directional. Distance @f$ d @f$ is in meters.
*/
class MAGNUM_TRADE_EXPORT LightData {
    public:
        #ifdef MAGNUM_BUILD_DEPRECATED
        /** @brief @copybrief LightType
         * @m_deprecated_since_latest Use @ref LightType instead.
         */
        typedef CORRADE_DEPRECATED("use LightType instead") LightType Type;
        #endif

        /**
         * @brief Constructor
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param attenuation       Constant, linear and quadratic light
         *      attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
         *      for an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param range             Light range, after which the intensity is
         *      considered to be zero. Expected to be @ref Constants::inf() for
         *      an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param innerConeAngle    Inner cone angle. Expected to be greater
         *      than or equal to @cpp 0.0_degf @ce and less than or equal to
         *      @p outerConeAngle for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param outerConeAngle    Outer cone angle. Expected to be greater
         *      than or equal to @p innerConeAngle and less than or equal to
         *      @cpp 360.0_degf @ce for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * This is a combined constructor including both attenuation and range
         * parameters. Use @ref LightData(LightType, const Color3&, Float, const Vector3&, Rad, Rad, const void*)
         * for light data defined by just attenuation parameters and
         * @ref LightData(LightType, const Color3&, Float, Float, Rad, Rad, const void*)
         * for light data defined by a range alone, and
         * @ref LightData(LightType, const Color3&, Float, Rad, Rad, const void*)
         * for an implicit inverse square attenuation. See
         * @ref Trade-LightData-attenuation for more information.
         *
         * For lights other than spot it may be more convenient to use
         * @ref LightData(LightType, const Color3&, Float, const Vector3&, Float, const void*)
         * and friends instead.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, const Vector3& attenuation, Float range, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct with implicit cone angles
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param attenuation       Constant, linear and quadratic light
         *      attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
         *      for an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param range             Light range, after which the intensity is
         *      considered to be zero. Expected to be @ref Constants::inf() for
         *      an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * This is a combined constructor including both attenuation and range
         * parameters. Use @ref LightData(LightType, const Color3&, Float, const Vector3&, const void*)
         * for light data defined by just attenuation parameters and
         * @ref LightData(LightType, const Color3&, Float, Float, const void*)
         * for light data defined by a range alone, and
         * @ref LightData(LightType, const Color3&, Float, const void*) for an
         * implicit inverse square attenuation. See
         * @ref Trade-LightData-attenuation for more information.
         *
         * For a @ref LightType::Spot light, @ref innerConeAngle() is
         * implicitly set to @cpp 0.0_degf @ce and @ref outerConeAngle() to
         * @cpp 90.0_degf @ce, and both are @cpp 360.0_degf @ce otherwise. Use
         * @ref LightData(LightType, const Color3&, Float, const Vector3&, Float, Rad, Rad, const void*)
         * in order to specify cone angles as well.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, const Vector3& attenuation, Float range, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct attenuation-based light data
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param attenuation       Constant, linear and quadratic light
         *      attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
         *      for an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param innerConeAngle    Inner cone angle. Expected to be greater
         *      than or equal to @cpp 0.0_degf @ce and less than or equal to
         *      @p outerConeAngle for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param outerConeAngle    Inner cone angle. Expected to be greater
         *      than or equal to @p innerConeAngle and less than or equal to
         *      @cpp 360.0_degf @ce for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * The @ref range() is implicitly set to @ref Constants::inf(). See
         * @ref Trade-LightData-attenuation for more information.
         *
         * For lights other than spot it may be more convenient to use
         * @ref LightData(LightType, const Color3&, Float, const Vector3&, const void*)
         * instead.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, const Vector3& attenuation, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct attenuation-based light data with implicit cone angles
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param attenuation       Constant, linear and quadratic light
         *      attenuation factor. Expected to be @cpp {1.0f, 0.0f, 0.0f} @ce
         *      for an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * The @ref range() is implicitly set to @ref Constants::inf(). See
         * @ref Trade-LightData-attenuation for more information.
         *
         * For a @ref LightType::Spot light, @ref innerConeAngle() is
         * implicitly set to @cpp 0.0_degf @ce and @ref outerConeAngle() to
         * @cpp 90.0_degf @ce, and both are @cpp 360.0_degf @ce otherwise. Use
         * @ref LightData(LightType, const Color3&, Float, const Vector3&, Rad, Rad, const void*)
         * in order to specify cone angles as well.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, const Vector3& attenuation, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct range-based light data
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param range             Light range, after which the intensity is
         *      considered to be zero. Expected to be @ref Constants::inf() for
         *      an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param innerConeAngle    Inner cone angle. Expected to be greater
         *      than or equal to @cpp 0.0_degf @ce and less than or equal to
         *      @p outerConeAngle for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param outerConeAngle    Outer cone angle. Expected to be greater
         *      than or equal to @p innerConeAngle and less than or equal to
         *      @cpp 360.0_degf @ce for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * The @ref attenuation() is implicitly set to @cpp {1.0f, 0.0f, 1.0f} @ce
         * for a @ref LightType::Point and @ref LightType::Spot light and to
         * @cpp {1.0f, 0.0f, 0.0f} @ce for an @ref LightType::Ambient and
         * @ref LightType::Directional light. See
         * @ref Trade-LightData-attenuation for more information.
         *
         * For lights other than spot it may be more convenient to use
         * @ref LightData(LightType, const Color3&, Float, Float, const void*)
         * instead.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, Float range, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct range-based light data with implicit cone angles
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param range             Light range, after which the intensity is
         *      considered to be zero. Expected to be @ref Constants::inf() for
         *      an @ref LightType::Ambient and @ref LightType::Directional
         *      light.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * The @ref attenuation() is implicitly set to @cpp {1.0f, 0.0f, 1.0f} @ce
         * for a @ref LightType::Point and @ref LightType::Spot light and to
         * @cpp {1.0f, 0.0f, 0.0f} @ce for an @ref LightType::Ambient and
         * @ref LightType::Directional light. See
         * @ref Trade-LightData-attenuation for more information.
         *
         * For a @ref LightType::Spot light, @ref innerConeAngle() is
         * implicitly set to @cpp 0.0_degf @ce and @ref outerConeAngle() to
         * @cpp 90.0_degf @ce, and both are @cpp 360.0_degf @ce otherwise. Use
         * @ref LightData(LightType, const Color3&, Float, Float, Rad, Rad, const void*)
         * in order to specify cone angles as well.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, Float range, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct light data with implicit attenuation
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param innerConeAngle    Inner cone angle. Expected to be greater
         *      than or equal to @cpp 0.0_degf @ce and less than or equal to
         *      @p outerConeAngle for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param outerConeAngle    Outer cone angle. Expected to be greater
         *      than or equal to @p innerConeAngle and less than or equal to
         *      @cpp 360.0_degf @ce for a @ref LightType::Spot light,
         *      @cpp 360.0_degf @ce otherwise.
         * @param importerState     Importer-specific state
         * @m_since_latest
         *
         * The @ref attenuation() is implicitly set to @cpp {1.0f, 0.0f, 1.0f} @ce
         * for a @ref LightType::Point and @ref LightType::Spot light and to
         * @cpp {1.0f, 0.0f, 0.0f} @ce for an @ref LightType::Ambient and
         * @ref LightType::Directional light; @ref range() is always
         * @ref Constants::inf(). See @ref Trade-LightData-attenuation for more
         * information.
         *
         * For lights other than spot it may be more convenient to use
         * @ref LightData(LightType, const Color3&, Float, const void*)
         * instead.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, Rad innerConeAngle, Rad outerConeAngle, const void* importerState = nullptr) noexcept;

        /**
         * @brief Construct light data with implicit attenuation and cone angles
         * @param type              Light type
         * @param color             Light color
         * @param intensity         Light intensity
         * @param importerState     Importer-specific state
         *
         * The @ref attenuation() is implicitly set to @cpp {1.0f, 0.0f, 1.0f} @ce
         * for a @ref LightType::Point and @ref LightType::Spot light and to
         * @cpp {1.0f, 0.0f, 0.0f} @ce for an @ref LightType::Ambient and
         * @ref LightType::Directional light; @ref range() is always
         * @ref Constants::inf(). See @ref Trade-LightData-attenuation for more
         * information.
         *
         * For a @ref LightType::Spot light, @ref innerConeAngle() is
         * implicitly set to @cpp 0.0_degf @ce and @ref outerConeAngle() to
         * @cpp 90.0_degf @ce, and both are @cpp 360.0_degf @ce otherwise. Use
         * @ref LightData(LightType, const Color3&, Float, Rad, Rad, const void*)
         * in order to specify cone angles as well.
         */
        explicit LightData(LightType type, const Color3& color, Float intensity, const void* importerState = nullptr) noexcept;

        /** @brief Copying is not allowed */
        LightData(const LightData&) = delete;

        /** @brief Move constructor */
        LightData(LightData&&) noexcept = default;

        /** @brief Copying is not allowed */
        LightData& operator=(const LightData&) = delete;

        /** @brief Move assignment */
        LightData& operator=(LightData&&) noexcept = default;

        /** @brief Light type */
        LightType type() const { return _type; }

        /** @brief Light color */
        Color3 color() const { return _color; }

        /**
         * @brief Light intensity
         *
         * Defined in *candela* (lm/sr) for @ref LightType::Point and
         * @ref LightType::Spot, and in *lux* (lm/m<sup>2</sup>) for
         * @ref LightType::Directional.
         */
        Float intensity() const { return _intensity; }

        /**
         * @brief Constant, linear and quadratic light attenuation
         * @m_since_latest
         *
         * Values of @f$ \color{m-success} K_c @f$,
         * @f$ \color{m-success} K_l @f$ and @f$ \color{m-success} K_q @f$ in
         * the @ref Trade-LightData-attenuation "attenuation equation". Always
         * @cpp {1.0f, 0.0f, 0.0f} @ce for an @ref LightType::Ambient and
         * @ref LightType::Directional light, set to
         * @cpp {1.0f, 0.0f, 1.0f} @ce for range-based attenuation --- and if
         * @ref range() is @ref Constants::inf() as well, the attenuation
         * equation is simply @f$ F_{att} = \frac{1}{1 + d^2} @f$.
         */
        Vector3 attenuation() const { return _attenuation; }

        /**
         * @brief Light range
         * @m_since_latest
         *
         * Value of @f$ \color{m-info} R @f$ in
         * the @ref Trade-LightData-attenuation "attenuation equation". If set
         * to @ref Constants::inf(), then:
         *
         * -    if @ref attenuation() is @cpp {1.0f, 0.0f, 1.0f} @ce, the
         *      attenuation equation is @f$ F_{att} = \frac{1}{1 + d^2} @f$;
         * -    if @ref attenuation() is @cpp {1.0f, 0.0f, 0.0f} @ce, the
         *      attenuation equation is @f$ F_{att} = 1 @f$.
         *
         * The latter is always the case for a @ref LightType::Directional
         * light.
         */
        Float range() const { return _range; }

        /**
         * @brief Inner cone angle
         * @m_since_latest
         *
         * For a @ref LightType::Spot light, it's always less than
         * @ref outerConeAngle(). For a @ref LightType::Directional or
         * @ref LightType::Point light it's always @cpp 360.0_degf @ce.
         */
        Rad innerConeAngle() const { return _innerConeAngle; }

        /**
         * @brief Outer cone angle
         * @m_since_latest
         *
         * For a @ref LightType::Spot light, it's always greater than
         * @ref outerConeAngle() and less than or equal to @cpp 90.0_degf @ce.
         * For a @ref LightType::Directional or @ref LightType::Point light
         * it's always @cpp 360.0_degf @ce.
         */
        Rad outerConeAngle() const { return _outerConeAngle; }

        /**
         * @brief Importer-specific state
         *
         * See @ref AbstractImporter::importerState() for more information.
         */
        const void* importerState() const { return _importerState; }

    private:
        LightType _type;
        Vector3 _color;
        Float _intensity;
        Vector3 _attenuation;
        Float _range;
        Rad _innerConeAngle, _outerConeAngle;
        const void* _importerState;
};

}}

#endif
